package org.zero.onlinesubject.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mysql.cj.util.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.zero.onlinesubject.common.constant.RedisConstant;
import org.zero.onlinesubject.common.vo.MessageNotifyVo;
import org.zero.onlinesubject.entity.*;
import org.zero.onlinesubject.mapper.MessageTypeMapper;
import org.zero.onlinesubject.mapper.TaskTypeMapper;
import org.zero.onlinesubject.service.MessageNotifyService;
import org.zero.onlinesubject.mapper.MessageNotifyMapper;
import org.springframework.stereotype.Service;
import org.zero.onlinesubject.service.UserService;
import org.zero.onlinesubject.utils.UserContextHolder;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *
 */
@Service
public class MessageNotifyServiceImpl extends ServiceImpl<MessageNotifyMapper, MessageNotify>
    implements MessageNotifyService{

    @Resource
    private MessageTypeMapper messageTypeMapper;

    @Resource
    private TaskTypeMapper taskTypeMapper;

    @Resource
    private MessageNotifyMapper messageNotifyMapper;

    @Resource
    private UserService userService;

    @Resource
    private Executor taskExecutor;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<MessageType> getMsgTypeList() {
        String json = stringRedisTemplate.opsForValue().get(RedisConstant.MSG_TYPE_PREFIX);
        if(!StringUtils.isNullOrEmpty(json)){
            return JSON.parseObject(json, new TypeReference<List<MessageType>>() {
            });
        }
        List<MessageType> typeList = messageTypeMapper.selectList(null);
        stringRedisTemplate.opsForValue().set(RedisConstant.MSG_TYPE_PREFIX,JSON.toJSONString(typeList),2,TimeUnit.HOURS);
        return typeList;
    }

    @Override
    public List<MessageNotifyVo> getMessages() throws ExecutionException, InterruptedException, TimeoutException {
        User user = UserContextHolder.get();
        // 获取消息列表
        CompletableFuture<List<MessageNotify>> f1 = CompletableFuture.supplyAsync(() -> {
            // 先查缓存，缓解数据库压力
            Map<Object, Object> objMap = stringRedisTemplate.opsForHash().entries(RedisConstant.MESSAGE_PREFIX + user.getUserid());
            if (objMap.isEmpty()) {
                QueryWrapper<MessageNotify> wrapper = new QueryWrapper<>();
                wrapper.eq("RecepientID", user.getUserid());
                List<MessageNotify> messages = messageNotifyMapper.selectList(wrapper);
                Map<String, String> map = new HashMap<>();
                messages.forEach(m -> {
                    map.put(m.getNotifyid() + "", JSON.toJSONString(m));
                });

//                stringRedisTemplate.opsForValue().set(RedisConstant.MESSAGE_PREFIX+user.getUserid(),JSON.toJSONString(messages),10,TimeUnit.MINUTES);
                stringRedisTemplate.opsForHash().putAll(RedisConstant.MESSAGE_PREFIX + user.getUserid(), map);
                stringRedisTemplate.expire(RedisConstant.MESSAGE_PREFIX + user.getUserid(), 10, TimeUnit.MINUTES);
                return messages;
            }
            Collection<Object> objects = objMap.values();
            return objects.stream().map(obj -> {
                return JSON.parseObject((String) obj, MessageNotify.class);
            }).collect(Collectors.toList());
        }, taskExecutor);

        CompletableFuture<Map<String, String>> f2 = CompletableFuture.supplyAsync(() -> {
            // 优先查询redis
            String json = stringRedisTemplate.opsForValue().get(RedisConstant.TASK_TYPE_PREFIX + "MessageNotify");
            List<TaskType> taskTypes = null;
            if(StringUtils.isNullOrEmpty(json)){
                taskTypes = taskTypeMapper.selectList(null);
                stringRedisTemplate.opsForValue().set(RedisConstant.TASK_TYPE_PREFIX+"MessageNotify", JSON.toJSONString(taskTypes),10,TimeUnit.MINUTES);
            }else{
                taskTypes = JSON.parseObject(json,new TypeReference<List<TaskType>>() {});
            }

            Map<String, String> map = new HashMap<>();
            taskTypes.forEach(t -> {
                map.put(t.getTypecode(), t.getName());
            });
            return map;
        }, taskExecutor);


        // 将消息列表进行转换
        CompletableFuture<List<MessageNotifyVo>> f3 = f1.thenCombine(f2, (list, typeMap) -> {
            List<String> userIds = list.stream().map(MessageNotify::getSenderid).distinct().collect(Collectors.toList());
            // 使用redis的hash结构来优化查询效率
            // 1. 先判断要查询的所有内容在 hash key 对应的 field 中是否都存在
            Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(RedisConstant.ID_UNAME_PREFIX + user.getUserid());
            List<String> diff = new ArrayList<>();

            userIds.forEach(id->{
                if(!map.containsKey(id)){
                    diff.add(id);
                }
            });
            if(!diff.isEmpty()){
                QueryWrapper<User> mapWrapper = new QueryWrapper<>();
                mapWrapper.select("UserID", "UserName").in("UserID", diff);
                List<User> users = userService.list(mapWrapper);
                users.forEach(u -> {
                    map.put(u.getUserid(), u.getUsername());
                });
                String key = RedisConstant.ID_UNAME_PREFIX + user.getUserid();
                stringRedisTemplate.opsForHash().putAll(key,map);
                stringRedisTemplate.expire(key,10,TimeUnit.MINUTES);
            }

            return list.stream().map(m -> {
                MessageNotifyVo vo = new MessageNotifyVo();
                vo.setNotifyid(m.getNotifyid());
                vo.setTaskid(m.getTaskid());
                vo.setNotifydate(m.getNotifydate());
                vo.setIsread(m.getIsread());
                vo.setLevel(m.getLevel());
                vo.setNotifytitle(m.getNotifytitle());
                vo.setSender((String) map.get(m.getSenderid()));
                vo.setTypename("【" + typeMap.get(m.getTypecode()) + "】");
                return vo;
            }).collect(Collectors.toList());
        });

        return f3.get(3, TimeUnit.SECONDS);
    }

    @Override
    public List<MessageNotifyVo> getMessageByName(String msgName,Boolean isRead, Integer current, Integer size) {
        User user = UserContextHolder.get();
        String userId = user.getUserid();
        Integer roleid = user.getRoleid();
        msgName = "%" + msgName + "%";
        List<MessageNotifyVo> list = null;
        if(roleid == 3){
            list = messageNotifyMapper.getMsgBySender(userId,msgName,isRead,current,size);
        } else {
            list = messageNotifyMapper.getMessageByName(userId,msgName,isRead,current, size);
        }

        Map<String, String> map = getMsgTypeList().stream().collect(Collectors.toMap(MessageType::getTypecode, MessageType::getName));
        list.forEach(item->{
            item.setTypename(map.get(item.getTypename()));
        });
        return list;
    }

    @Override
    public Boolean updateMsgState(Integer msgId, String userid) {
        UpdateWrapper<MessageNotify> wrapper = new UpdateWrapper<>();
        wrapper.eq("NotifyID",msgId).eq("IsRead",false).set("IsRead",true).set("Level","success").last(" limit 1");
        int rows = messageNotifyMapper.update(wrapper);

        if(rows==0) return false;
        String json = stringRedisTemplate.opsForHash().get(RedisConstant.MESSAGE_PREFIX + userid, msgId + "").toString();
        if(!StringUtils.isNullOrEmpty(json)){
            MessageNotify messageNotify = JSON.parseObject(json, MessageNotify.class);
            messageNotify.setIsread(true);
            messageNotify.setLevel("success");
            String val = JSON.toJSONString(messageNotify);
            stringRedisTemplate.opsForHash().put(RedisConstant.MESSAGE_PREFIX + userid,msgId+"",val);
        }
        return true;
    }

    @Override
    public MessageNotifyVo getNotifyInfo(Integer notifyid) {
        User user = UserContextHolder.get();
        String key = RedisConstant.MESSAGE_PREFIX + user.getUserid();
        Object obj = stringRedisTemplate.opsForHash().get(key, notifyid+"");
        String json = (String)obj;
        String json2 = stringRedisTemplate.opsForValue().get(RedisConstant.TASK_TYPE_PREFIX + "MessageNotify");

        MessageNotify msg = JSON.parseObject(json, MessageNotify.class);
        if(msg==null) return null;
        List<TaskType> taskTypes = JSON.parseObject(json2, new TypeReference<List<TaskType>>() {});
        Map<String, String> map = taskTypes.stream().collect(Collectors.toMap(TaskType::getTypecode, TaskType::getName));
        MessageNotifyVo vo = new MessageNotifyVo();
        BeanUtils.copyProperties(msg,vo);
        vo.setTypename("【" + map.get(msg.getTypecode())+"】");
        vo.setIsread(true);
        return vo;
    }
}

